listitem: Make this a GObject
authorBenjamin Otte <otte@redhat.com>
Fri, 1 Nov 2019 06:31:38 +0000 (07:31 +0100)
committerMatthias Clasen <mclasen@redhat.com>
Sat, 30 May 2020 23:26:46 +0000 (19:26 -0400)
This splits GtkListItem into 2 parts:

1. GtkListItem
   This is purely a GObject with public API for developers who want to
   populate lists. There is no chance to cause conflict with GtkWidget
   properties that the list implementation assumed control over and
   defines a clear boundary.
2. GtkListItemWidget
   The widget part of the listitem. This is not only fully in control of
   the list machinery, the machinery can also use different widget
   implementations for different list widgets like I inted to for
   GtkColumnView.

docs/reference/gtk/meson.build
gtk/gtklistbase.c
gtk/gtklistbase.h
gtk/gtklistitem.c
gtk/gtklistitemmanager.c
gtk/gtklistitemmanagerprivate.h
gtk/gtklistitemprivate.h
gtk/gtklistitemwidget.c [new file with mode: 0644]
gtk/gtklistitemwidgetprivate.h [new file with mode: 0644]
gtk/meson.build
tests/testlistview.c

index aa1bfdf0578a23524c086d886eca325fdf0c69bf..7c6ae15dad4e0e8e9e18d67288d5f2301166b00f 100644 (file)
@@ -137,6 +137,7 @@ private_headers = [
   'gtklistitemprivate.h',
   'gtklistitemfactoryprivate.h',
   'gtklistitemmanagerprivate.h',
+  'gtklistitemwidgetprivate.h',
   'gtklockbuttonprivate.h',
   'gtkmagnifierprivate.h',
   'gtkmediafileprivate.h',
index e3603e35e1b1dcb6a8ffd5a07902b0060abcba9e..8ec9bc8784f8b07843a483612d57ab73e687f874 100644 (file)
@@ -23,6 +23,7 @@
 
 #include "gtkadjustment.h"
 #include "gtkintl.h"
+#include "gtklistitemwidgetprivate.h"
 #include "gtkorientableprivate.h"
 #include "gtkscrollable.h"
 #include "gtksingleselection.h"
@@ -749,10 +750,10 @@ gtk_list_base_update_focus_tracker (GtkListBase *self)
   guint pos;
 
   focus_child = gtk_widget_get_focus_child (GTK_WIDGET (self));
-  if (!GTK_IS_LIST_ITEM (focus_child))
+  if (!GTK_IS_LIST_ITEM_WIDGET (focus_child))
     return;
 
-  pos = gtk_list_item_get_position (GTK_LIST_ITEM (focus_child));
+  pos = gtk_list_item_widget_get_position (GTK_LIST_ITEM_WIDGET (focus_child));
   if (pos != gtk_list_item_tracker_get_position (priv->item_manager, priv->focus))
     {
       gtk_list_item_tracker_set_position (priv->item_manager,
index 7b33edeec7c586a26e15fa50aa69d91239b57b50..24e616184c29c9113de80e7836aa591b7484ac2d 100644 (file)
@@ -25,7 +25,6 @@
 #endif
 
 #include <gtk/gtkwidget.h>
-#include <gtk/gtklistitem.h>
 
 G_BEGIN_DECLS
 
index 05b5fa1e8ea24348a3c5ac1aade26eb912638a1d..ee50eb42ab9939e90af0f5b694c8d10c71fa6cc0 100644 (file)
 
 #include "gtklistitemprivate.h"
 
-#include "gtkbinlayout.h"
-#include "gtkcssnodeprivate.h"
-#include "gtkeventcontrollerfocus.h"
-#include "gtkgestureclick.h"
 #include "gtkintl.h"
-#include "gtkmain.h"
-#include "gtkwidget.h"
-#include "gtkwidgetprivate.h"
 
 /**
  * SECTION:gtklistitem
  *    The #GtkListItem:item property is not %NULL.
  */
 
-struct _GtkListItem
-{
-  GtkWidget parent_instance;
-
-  GObject *item;
-  GtkWidget *child;
-  guint position;
-
-  guint activatable : 1;
-  guint selectable : 1;
-  guint selected : 1;
-};
-
 struct _GtkListItemClass
 {
-  GtkWidgetClass parent_class;
-
-  void          (* activate_signal)                             (GtkListItem            *self);
+  GObjectClass parent_class;
 };
 
 enum
@@ -86,81 +64,17 @@ enum
   N_PROPS
 };
 
-enum
-{
-  ACTIVATE_SIGNAL,
-  LAST_SIGNAL
-};
-
-G_DEFINE_TYPE (GtkListItem, gtk_list_item, GTK_TYPE_WIDGET)
+G_DEFINE_TYPE (GtkListItem, gtk_list_item, G_TYPE_OBJECT)
 
 static GParamSpec *properties[N_PROPS] = { NULL, };
-static guint signals[LAST_SIGNAL] = { 0 };
-
-static void
-gtk_list_item_activate_signal (GtkListItem *self)
-{
-  if (!self->activatable)
-    return;
-
-  gtk_widget_activate_action (GTK_WIDGET (self),
-                              "list.activate-item",
-                              "u",
-                              self->position);
-}
-
-static gboolean
-gtk_list_item_focus (GtkWidget        *widget,
-                     GtkDirectionType  direction)
-{
-  GtkListItem *self = GTK_LIST_ITEM (widget);
-
-  /* The idea of this function is the following:
-   * 1. If any child can take focus, do not ever attempt
-   *    to take focus.
-   * 2. Otherwise, if this item is selectable or activatable,
-   *    allow focusing this widget.
-   *
-   * This makes sure every item in a list is focusable for
-   * activation and selection handling, but no useless widgets
-   * get focused and moving focus is as fast as possible.
-   */
-  if (self->child)
-    {
-      if (gtk_widget_get_focus_child (widget))
-        return FALSE;
-      if (gtk_widget_child_focus (self->child, direction))
-        return TRUE;
-    }
-
-  if (gtk_widget_is_focus (widget))
-    return FALSE;
-
-  if (!gtk_widget_get_can_focus (widget) ||
-      !self->selectable)
-    return FALSE;
-
-  return gtk_widget_grab_focus (widget);
-}
-
-static gboolean
-gtk_list_item_grab_focus (GtkWidget *widget)
-{
-  GtkListItem *self = GTK_LIST_ITEM (widget);
-
-  if (self->child && gtk_widget_grab_focus (self->child))
-    return TRUE;
-
-  return GTK_WIDGET_CLASS (gtk_list_item_parent_class)->grab_focus (widget);
-}
 
 static void
 gtk_list_item_dispose (GObject *object)
 {
   GtkListItem *self = GTK_LIST_ITEM (object);
 
-  g_assert (self->item == NULL);
-  g_clear_pointer (&self->child, gtk_widget_unparent);
+  g_assert (self->owner == NULL); /* would hold a reference */
+  g_clear_object (&self->child);
 
   G_OBJECT_CLASS (gtk_list_item_parent_class)->dispose (object);
 }
@@ -233,36 +147,11 @@ gtk_list_item_set_property (GObject      *object,
     }
 }
 
-static void
-gtk_list_item_select_action (GtkWidget  *widget,
-                             const char *action_name,
-                             GVariant   *parameter)
-{
-  GtkListItem *self = GTK_LIST_ITEM (widget);
-  gboolean modify, extend;
-
-  if (!self->selectable)
-    return;
-
-  g_variant_get (parameter, "(bb)", &modify, &extend);
-
-  gtk_widget_activate_action (GTK_WIDGET (self),
-                              "list.select-item",
-                              "(ubb)",
-                              self->position, modify, extend);
-}
-
 static void
 gtk_list_item_class_init (GtkListItemClass *klass)
 {
-  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 
-  klass->activate_signal = gtk_list_item_activate_signal;
-
-  widget_class->focus = gtk_list_item_focus;
-  widget_class->grab_focus = gtk_list_item_grab_focus;
-
   gobject_class->dispose = gtk_list_item_dispose;
   gobject_class->get_property = gtk_list_item_get_property;
   gobject_class->set_property = gtk_list_item_set_property;
@@ -340,197 +229,25 @@ gtk_list_item_class_init (GtkListItemClass *klass)
                           G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
   g_object_class_install_properties (gobject_class, N_PROPS, properties);
-
-  /**
-   * GtkListItem::activate-signal:
-   *
-   * This is a keybinding signal, which will cause this row to be activated.
-   *
-   * Do not use it, it is an implementation detail.
-   *
-   * If you want to be notified when the user activates a listitem (by key or not),
-   * look at the list widget this item is contained in.
-   */
-  signals[ACTIVATE_SIGNAL] =
-    g_signal_new (I_("activate-keybinding"),
-                  G_OBJECT_CLASS_TYPE (gobject_class),
-                  G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
-                  G_STRUCT_OFFSET (GtkListItemClass, activate_signal),
-                  NULL, NULL,
-                  NULL,
-                  G_TYPE_NONE, 0);
-
-  widget_class->activate_signal = signals[ACTIVATE_SIGNAL];
-
-  /**
-   * GtkListItem|listitem.select:
-   * @modify: %TRUE to toggle the existing selection, %FALSE to select
-   * @extend: %TRUE to extend the selection
-   *
-   * Changes selection if the item is selectable.
-   * If the item is not selectable, nothing happens.
-   *
-   * This function will emit the list.select-item action and the resulting
-   * behavior, in particular the interpretation of @modify and @extend
-   * depends on the view containing this listitem. See for example
-   * GtkListView|list.select-item or GtkGridView|list.select-item.
-   */
-  gtk_widget_class_install_action (widget_class,
-                                   "listitem.select",
-                                   "(bb)",
-                                   gtk_list_item_select_action);
-
-  gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_Return, 0,
-                                       "activate-keybinding", 0);
-  gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_ISO_Enter, 0,
-                                       "activate-keybinding", 0);
-  gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_KP_Enter, 0,
-                                       "activate-keybinding", 0);
-
-  /* note that some of these may get overwritten by child widgets,
-   * such as GtkTreeExpander */
-  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, 0,
-                                       "listitem.select", "(bb)", TRUE, FALSE);
-  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, GDK_CONTROL_MASK,
-                                       "listitem.select", "(bb)", TRUE, FALSE);
-  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, GDK_SHIFT_MASK,
-                                       "listitem.select", "(bb)", TRUE, FALSE);
-  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
-                                       "listitem.select", "(bb)", TRUE, FALSE);
-  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, 0,
-                                       "listitem.select", "(bb)", TRUE, FALSE);
-  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, GDK_CONTROL_MASK,
-                                       "listitem.select", "(bb)", TRUE, FALSE);
-  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, GDK_SHIFT_MASK,
-                                       "listitem.select", "(bb)", TRUE, FALSE);
-  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
-                                       "listitem.select", "(bb)", TRUE, FALSE);
-
-  /* This gets overwritten by gtk_list_item_new() but better safe than sorry */
-  gtk_widget_class_set_css_name (widget_class, I_("row"));
-  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
-}
-
-static void
-gtk_list_item_click_gesture_pressed (GtkGestureClick *gesture,
-                                     int              n_press,
-                                     double           x,
-                                     double           y,
-                                     GtkListItem     *self)
-{
-  GtkWidget *widget = GTK_WIDGET (self);
-
-  if (!self->selectable && !self->activatable)
-    {
-      gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
-      return;
-    }
-
-  if (self->selectable)
-    {
-      GdkModifierType state;
-      GdkEvent *event;
-      gboolean extend, modify;
-
-      event = gtk_gesture_get_last_event (GTK_GESTURE (gesture),
-                                          gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)));
-      state = gdk_event_get_modifier_state (event);
-      extend = (state & GDK_SHIFT_MASK) != 0;
-      modify = (state & GDK_CONTROL_MASK) != 0;
-
-      gtk_widget_activate_action (widget,
-                                  "list.select-item",
-                                  "(ubb)",
-                                  self->position, modify, extend);
-    }
-
-  if (self->activatable)
-    {
-      if (n_press == 2)
-        {
-          gtk_widget_activate_action (widget,
-                                      "list.activate-item",
-                                      "u",
-                                      self->position);
-        }
-    }
-
-  gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_ACTIVE, FALSE);
-
-  if (gtk_widget_get_focus_on_click (widget))
-    gtk_widget_grab_focus (widget);
-}
-
-static void
-gtk_list_item_enter_cb (GtkEventControllerFocus *controller,
-                        GtkListItem             *self)
-{
-  GtkWidget *widget = GTK_WIDGET (self);
-
-  gtk_widget_activate_action (widget,
-                              "list.scroll-to-item",
-                              "u",
-                              self->position);
-}
-
-static void
-gtk_list_item_click_gesture_released (GtkGestureClick *gesture,
-                                      int              n_press,
-                                      double           x,
-                                      double           y,
-                                      GtkListItem     *self)
-{
-  gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE);
-}
-
-static void
-gtk_list_item_click_gesture_canceled (GtkGestureClick  *gesture,
-                                      GdkEventSequence *sequence,
-                                      GtkListItem      *self)
-{
-  gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE);
 }
 
 static void
 gtk_list_item_init (GtkListItem *self)
 {
-  GtkEventController *controller;
-  GtkGesture *gesture;
-
   self->selectable = TRUE;
   self->activatable = TRUE;
-  gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
-
-  gesture = gtk_gesture_click_new ();
-  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
-                                              GTK_PHASE_BUBBLE);
-  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture),
-                                     FALSE);
-  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture),
-                                 GDK_BUTTON_PRIMARY);
-  g_signal_connect (gesture, "pressed",
-                    G_CALLBACK (gtk_list_item_click_gesture_pressed), self);
-  g_signal_connect (gesture, "released",
-                    G_CALLBACK (gtk_list_item_click_gesture_released), self);
-  g_signal_connect (gesture, "cancel",
-                    G_CALLBACK (gtk_list_item_click_gesture_canceled), self);
-  gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
-
-  controller = gtk_event_controller_focus_new ();
-  g_signal_connect (controller, "enter", G_CALLBACK (gtk_list_item_enter_cb), self);
-  gtk_widget_add_controller (GTK_WIDGET (self), controller);
 }
 
 GtkListItem *
-gtk_list_item_new (const char *css_name)
+gtk_list_item_new (GtkListItemWidget *owner)
 {
   GtkListItem *result;
 
-  g_return_val_if_fail (css_name != NULL, NULL);
+  g_return_val_if_fail (owner != NULL, NULL);
 
   result = g_object_new (GTK_TYPE_LIST_ITEM,
-                         "css-name", css_name,
                          NULL);
+  result->owner = owner;
 
   return result;
 }
@@ -590,12 +307,17 @@ gtk_list_item_set_child (GtkListItem *self,
   if (self->child == child)
     return;
 
-  g_clear_pointer (&self->child, gtk_widget_unparent);
+  if (self->child && self->owner)
+    gtk_list_item_widget_remove_child (self->owner, self->child);
+
+  g_clear_object (&self->child);
 
   if (child)
     {
-      gtk_widget_insert_after (child, GTK_WIDGET (self), NULL);
+      g_object_ref_sink (child);
       self->child = child;
+      if (self->owner)
+        gtk_list_item_widget_add_child (self->owner, child);
     }
 
   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]);
@@ -615,8 +337,6 @@ gtk_list_item_set_item (GtkListItem *self,
   if (item)
     self->item = g_object_ref (item);
 
-  gtk_css_node_invalidate (gtk_widget_get_css_node (GTK_WIDGET (self)), GTK_CSS_CHANGE_ANIMATIONS);
-
   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]);
 }
 
@@ -680,11 +400,6 @@ gtk_list_item_set_selected (GtkListItem *self,
 
   self->selected = selected;
 
-  if (selected)
-    gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED, FALSE);
-  else
-    gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED);
-
   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]);
 }
 
index 65705260ead01120f2a790a2bb868a513b6cd152..1236aaf91ec68e1f2210b7df48f6a220c5d9f173 100644 (file)
@@ -21,7 +21,7 @@
 
 #include "gtklistitemmanagerprivate.h"
 
-#include "gtklistitemprivate.h"
+#include "gtklistitemwidgetprivate.h"
 #include "gtkwidgetprivate.h"
 
 #define GTK_LIST_VIEW_MAX_LIST_ITEMS 200
@@ -47,7 +47,7 @@ struct _GtkListItemManagerClass
 struct _GtkListItemTracker
 {
   guint position;
-  GtkListItem *widget;
+  GtkListItemWidget *widget;
   guint n_before;
   guint n_after;
 };
@@ -595,7 +595,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel         *model,
       if (tracker->widget == NULL)
         continue;
 
-      if (g_hash_table_lookup (change, gtk_list_item_get_item (tracker->widget)))
+      if (g_hash_table_lookup (change, gtk_list_item_widget_get_item (tracker->widget)))
         break;
     }
 
@@ -678,7 +678,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel         *model,
         }
       else if (tracker->position >= position)
         {
-          if (g_hash_table_lookup (change, gtk_list_item_get_item (tracker->widget)))
+          if (g_hash_table_lookup (change, gtk_list_item_widget_get_item (tracker->widget)))
             {
               /* The item is gone. Guess a good new position */
               tracker->position = position + (tracker->position - position) * added / removed;
@@ -696,7 +696,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel         *model,
               /* item was put in its right place in the expensive loop above,
                * and we updated its position while at it. So grab it from there.
                */
-              tracker->position = gtk_list_item_get_position (tracker->widget);
+              tracker->position = gtk_list_item_widget_get_position (tracker->widget);
             }
         }
       else
@@ -723,7 +723,7 @@ gtk_list_item_manager_model_items_changed_cb (GListModel         *model,
       item = gtk_list_item_manager_get_nth (self, tracker->position, NULL);
       g_assert (item != NULL);
       g_assert (item->widget);
-      tracker->widget = GTK_LIST_ITEM (item->widget);
+      tracker->widget = GTK_LIST_ITEM_WIDGET (item->widget);
     }
 
   g_hash_table_iter_init (&iter, change);
@@ -850,7 +850,7 @@ gtk_list_item_manager_set_factory (GtkListItemManager *self,
 
       item = gtk_list_item_manager_get_nth (self, tracker->position, NULL);
       g_assert (item);
-      tracker->widget = GTK_LIST_ITEM (item->widget);
+      tracker->widget = GTK_LIST_ITEM_WIDGET (item->widget);
     }
 }
 
@@ -922,23 +922,21 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self,
                                          guint               position,
                                          GtkWidget          *prev_sibling)
 {
-  GtkListItem *result;
+  GtkWidget *result;
   gpointer item;
   gboolean selected;
 
   g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
   g_return_val_if_fail (prev_sibling == NULL || GTK_IS_WIDGET (prev_sibling), NULL);
 
-  result = gtk_list_item_new (self->item_css_name);
-  if (self->factory)
-    gtk_list_item_factory_setup (self->factory, result);
+  result = gtk_list_item_widget_new (self->factory,
+                                     self->item_css_name);
 
   item = g_list_model_get_item (G_LIST_MODEL (self->model), position);
   selected = gtk_selection_model_is_selected (self->model, position);
-  if (self->factory)
-    gtk_list_item_factory_bind (self->factory, result, position, item, selected);
+  gtk_list_item_widget_bind (GTK_LIST_ITEM_WIDGET (result), position, item, selected);
   g_object_unref (item);
-  gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling);
+  gtk_widget_insert_after (result, self->widget, prev_sibling);
 
   return GTK_WIDGET (result);
 }
@@ -964,7 +962,7 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self,
                                                guint               position,
                                                GtkWidget          *prev_sibling)
 {
-  GtkListItem *result;
+  GtkWidget *result;
   gpointer item;
 
   g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
@@ -974,11 +972,10 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self,
   item = g_list_model_get_item (G_LIST_MODEL (self->model), position);
   if (g_hash_table_steal_extended (change, item, NULL, (gpointer *) &result))
     {
-      if (self->factory)
-        gtk_list_item_factory_update (self->factory, result, position, FALSE);
-      gtk_widget_insert_after (GTK_WIDGET (result), self->widget, prev_sibling);
+      gtk_list_item_widget_update (GTK_LIST_ITEM_WIDGET (result), position, FALSE);
+      gtk_widget_insert_after (result, self->widget, prev_sibling);
       /* XXX: Should we let the listview do this? */
-      gtk_widget_queue_resize (GTK_WIDGET (result));
+      gtk_widget_queue_resize (result);
     }
   else
     {
@@ -986,7 +983,7 @@ gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self,
     }
   g_object_unref (item);
 
-  return GTK_WIDGET (result);
+  return result;
 }
 
 /**
@@ -1013,8 +1010,7 @@ gtk_list_item_manager_move_list_item (GtkListItemManager     *self,
 
   item = g_list_model_get_item (G_LIST_MODEL (self->model), position);
   selected = gtk_selection_model_is_selected (self->model, position);
-  if (self->factory)
-    gtk_list_item_factory_rebind (self->factory, GTK_LIST_ITEM (list_item), position, item, selected);
+  gtk_list_item_widget_rebind (GTK_LIST_ITEM_WIDGET (list_item), position, item, selected);
   gtk_widget_insert_after (list_item, _gtk_widget_get_parent (list_item), prev_sibling);
   g_object_unref (item);
 }
@@ -1036,11 +1032,10 @@ gtk_list_item_manager_update_list_item (GtkListItemManager *self,
   gboolean selected;
 
   g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
-  g_return_if_fail (GTK_IS_LIST_ITEM (item));
+  g_return_if_fail (GTK_IS_LIST_ITEM_WIDGET (item));
 
   selected = gtk_selection_model_is_selected (self->model, position);
-  if (self->factory)
-    gtk_list_item_factory_update (self->factory, GTK_LIST_ITEM (item), position, selected);
+  gtk_list_item_widget_update (GTK_LIST_ITEM_WIDGET (item), position, selected);
 }
 
 /*
@@ -1060,21 +1055,16 @@ gtk_list_item_manager_release_list_item (GtkListItemManager *self,
                                          GtkWidget          *item)
 {
   g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
-  g_return_if_fail (GTK_IS_LIST_ITEM (item));
+  g_return_if_fail (GTK_IS_LIST_ITEM_WIDGET (item));
 
   if (change != NULL)
     {
-      if (g_hash_table_insert (change, gtk_list_item_get_item (GTK_LIST_ITEM (item)), item))
+      if (g_hash_table_insert (change, gtk_list_item_widget_get_item (GTK_LIST_ITEM_WIDGET (item)), item))
         return;
       
       g_warning ("FIXME: Handle the same item multiple times in the list.\nLars says this totally should not happen, but here we are.");
     }
 
-  if (self->factory)
-    {
-      gtk_list_item_factory_unbind (self->factory, GTK_LIST_ITEM (item));
-      gtk_list_item_factory_teardown (self->factory, GTK_LIST_ITEM (item));
-    }
   gtk_widget_unparent (item);
 }
 
@@ -1136,7 +1126,7 @@ gtk_list_item_tracker_set_position (GtkListItemManager *self,
 
   item = gtk_list_item_manager_get_nth (self, position, NULL);
   if (item)
-    tracker->widget = GTK_LIST_ITEM (item->widget);
+    tracker->widget = GTK_LIST_ITEM_WIDGET (item->widget);
 
   gtk_widget_queue_resize (self->widget);
 }
index a41ea844722889b2c676f79683c77b9c88882148..18cdc6d9ff9dc1325fda3ca138847a8beb74c04d 100644 (file)
@@ -23,7 +23,7 @@
 
 #include "gtk/gtktypes.h"
 
-#include "gtk/gtklistitemfactoryprivate.h"
+#include "gtk/gtklistitemfactory.h"
 #include "gtk/gtkrbtreeprivate.h"
 #include "gtk/gtkselectionmodel.h"
 
index 3a2ac463d145c8a1e1674312f0df851ece6900f4..5912e406bea72fc8a1c67104d36734fecd4c8c96 100644 (file)
 
 #include "gtklistitem.h"
 
-#include "gtklistitemmanagerprivate.h"
+#include "gtklistitemwidgetprivate.h"
 
 G_BEGIN_DECLS
 
-GtkListItem *   gtk_list_item_new                               (const char             *css_name);
+struct _GtkListItem
+{
+  GObject parent_instance;
+
+  GtkListItemWidget *owner; /* has a reference */
+
+  GObject *item;
+  GtkWidget *child;
+  guint position;
+
+  guint activatable : 1;
+  guint selectable : 1;
+  guint selected : 1;
+};
+
+GtkListItem *   gtk_list_item_new                               (GtkListItemWidget      *owner);
 
 void            gtk_list_item_set_item                          (GtkListItem            *self,
                                                                  gpointer                item);
diff --git a/gtk/gtklistitemwidget.c b/gtk/gtklistitemwidget.c
new file mode 100644 (file)
index 0000000..9aab4c0
--- /dev/null
@@ -0,0 +1,426 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#include "config.h"
+
+#include "gtklistitemwidgetprivate.h"
+
+#include "gtkbinlayout.h"
+#include "gtkcssnodeprivate.h"
+#include "gtkeventcontrollerfocus.h"
+#include "gtkgestureclick.h"
+#include "gtkintl.h"
+#include "gtklistitemfactoryprivate.h"
+#include "gtklistitemprivate.h"
+#include "gtkmain.h"
+#include "gtkwidget.h"
+#include "gtkwidgetprivate.h"
+
+enum
+{
+  ACTIVATE_SIGNAL,
+  LAST_SIGNAL
+};
+
+G_DEFINE_TYPE (GtkListItemWidget, gtk_list_item_widget, GTK_TYPE_WIDGET)
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+gtk_list_item_widget_activate_signal (GtkListItemWidget *self)
+{
+  if (!self->item->activatable)
+    return;
+
+  gtk_widget_activate_action (GTK_WIDGET (self),
+                              "list.activate-item",
+                              "u",
+                              self->item->position);
+}
+
+static gboolean
+gtk_list_item_widget_focus (GtkWidget        *widget,
+                            GtkDirectionType  direction)
+{
+  GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (widget);
+
+  /* The idea of this function is the following:
+   * 1. If any child can take focus, do not ever attempt
+   *    to take focus.
+   * 2. Otherwise, if this item is selectable or activatable,
+   *    allow focusing this widget.
+   *
+   * This makes sure every item in a list is focusable for
+   * activation and selection handling, but no useless widgets
+   * get focused and moving focus is as fast as possible.
+   */
+  if (self->item && self->item->child)
+    {
+      if (gtk_widget_get_focus_child (widget))
+        return FALSE;
+      if (gtk_widget_child_focus (self->item->child, direction))
+        return TRUE;
+    }
+
+  if (gtk_widget_is_focus (widget))
+    return FALSE;
+
+  if (!gtk_widget_get_can_focus (widget) ||
+      !self->item->selectable)
+    return FALSE;
+
+  return gtk_widget_grab_focus (widget);
+}
+
+static gboolean
+gtk_list_item_widget_grab_focus (GtkWidget *widget)
+{
+  GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (widget);
+
+  if (self->item->child && gtk_widget_grab_focus (self->item->child))
+    return TRUE;
+
+  return GTK_WIDGET_CLASS (gtk_list_item_widget_parent_class)->grab_focus (widget);
+}
+
+static void
+gtk_list_item_widget_dispose (GObject *object)
+{
+  GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (object);
+
+  if (self->item)
+    {
+      if (self->item->item)
+        gtk_list_item_factory_unbind (self->factory, self->item);
+      gtk_list_item_factory_teardown (self->factory, self->item);
+      self->item->owner = NULL;
+      g_clear_object (&self->item);
+    }
+  g_clear_object (&self->factory);
+
+  G_OBJECT_CLASS (gtk_list_item_widget_parent_class)->dispose (object);
+}
+
+static void
+gtk_list_item_widget_select_action (GtkWidget  *widget,
+                                    const char *action_name,
+                                    GVariant   *parameter)
+{
+  GtkListItemWidget *self = GTK_LIST_ITEM_WIDGET (widget);
+  gboolean modify, extend;
+
+  if (!self->item->selectable)
+    return;
+
+  g_variant_get (parameter, "(bb)", &modify, &extend);
+
+  gtk_widget_activate_action (GTK_WIDGET (self),
+                              "list.select-item",
+                              "(ubb)",
+                              self->item->position, modify, extend);
+}
+
+static void
+gtk_list_item_widget_class_init (GtkListItemWidgetClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  klass->activate_signal = gtk_list_item_widget_activate_signal;
+
+  widget_class->focus = gtk_list_item_widget_focus;
+  widget_class->grab_focus = gtk_list_item_widget_grab_focus;
+
+  gobject_class->dispose = gtk_list_item_widget_dispose;
+
+  signals[ACTIVATE_SIGNAL] =
+    g_signal_new (I_("activate-keybinding"),
+                  G_OBJECT_CLASS_TYPE (gobject_class),
+                  G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+                  G_STRUCT_OFFSET (GtkListItemWidgetClass, activate_signal),
+                  NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE, 0);
+
+  widget_class->activate_signal = signals[ACTIVATE_SIGNAL];
+
+  /**
+   * GtkListItem|listitem.select:
+   * @modify: %TRUE to toggle the existing selection, %FALSE to select
+   * @extend: %TRUE to extend the selection
+   *
+   * Changes selection if the item is selectable.
+   * If the item is not selectable, nothing happens.
+   *
+   * This function will emit the list.select-item action and the resulting
+   * behavior, in particular the interpretation of @modify and @extend
+   * depends on the view containing this listitem. See for example
+   * GtkListView|list.select-item or GtkGridView|list.select-item.
+   */
+  gtk_widget_class_install_action (widget_class,
+                                   "listitem.select",
+                                   "(bb)",
+                                   gtk_list_item_widget_select_action);
+
+  gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_Return, 0,
+                                       "activate-keybinding", 0);
+  gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_ISO_Enter, 0,
+                                       "activate-keybinding", 0);
+  gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_KP_Enter, 0,
+                                       "activate-keybinding", 0);
+
+  /* note that some of these may get overwritten by child widgets,
+   * such as GtkTreeExpander */
+  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, 0,
+                                       "listitem.select", "(bb)", TRUE, FALSE);
+  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, GDK_CONTROL_MASK,
+                                       "listitem.select", "(bb)", TRUE, FALSE);
+  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, GDK_SHIFT_MASK,
+                                       "listitem.select", "(bb)", TRUE, FALSE);
+  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_space, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
+                                       "listitem.select", "(bb)", TRUE, FALSE);
+  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, 0,
+                                       "listitem.select", "(bb)", TRUE, FALSE);
+  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, GDK_CONTROL_MASK,
+                                       "listitem.select", "(bb)", TRUE, FALSE);
+  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, GDK_SHIFT_MASK,
+                                       "listitem.select", "(bb)", TRUE, FALSE);
+  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Space, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
+                                       "listitem.select", "(bb)", TRUE, FALSE);
+
+  /* This gets overwritten by gtk_list_item_widget_new() but better safe than sorry */
+  gtk_widget_class_set_css_name (widget_class, I_("row"));
+  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+}
+
+static void
+gtk_list_item_widget_click_gesture_pressed (GtkGestureClick *gesture,
+                                            int              n_press,
+                                            double           x,
+                                            double           y,
+                                            GtkListItemWidget     *self)
+{
+  GtkWidget *widget = GTK_WIDGET (self);
+
+  if (!self->item->selectable && !self->item->activatable)
+    {
+      gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+      return;
+    }
+
+  if (self->item->selectable)
+    {
+      GdkModifierType state;
+      GdkEvent *event;
+      gboolean extend, modify;
+
+      event = gtk_gesture_get_last_event (GTK_GESTURE (gesture),
+                                          gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)));
+      state = gdk_event_get_modifier_state (event);
+      extend = (state & GDK_SHIFT_MASK) != 0;
+      modify = (state & GDK_CONTROL_MASK) != 0;
+
+      gtk_widget_activate_action (GTK_WIDGET (self),
+                                  "list.select-item",
+                                  "(ubb)",
+                                  self->item->position, modify, extend);
+    }
+
+  if (self->item->activatable)
+    {
+      if (n_press == 2)
+        {
+          gtk_widget_activate_action (GTK_WIDGET (self),
+                                      "list.activate-item",
+                                      "u",
+                                      self->item->position);
+        }
+    }
+
+  gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_ACTIVE, FALSE);
+
+  if (gtk_widget_get_focus_on_click (widget))
+    gtk_widget_grab_focus (widget);
+}
+
+static void
+gtk_list_item_widget_enter_cb (GtkEventControllerFocus *controller,
+                               GtkListItemWidget       *self)
+{
+  GtkWidget *widget = GTK_WIDGET (self);
+
+  gtk_widget_activate_action (widget,
+                              "list.scroll-to-item",
+                              "u",
+                              self->item->position);
+}
+
+static void
+gtk_list_item_widget_click_gesture_released (GtkGestureClick   *gesture,
+                                             int                n_press,
+                                             double             x,
+                                             double             y,
+                                             GtkListItemWidget *self)
+{
+  gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE);
+}
+
+static void
+gtk_list_item_widget_click_gesture_canceled (GtkGestureClick   *gesture,
+                                             GdkEventSequence  *sequence,
+                                             GtkListItemWidget *self)
+{
+  gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE);
+}
+
+static void
+gtk_list_item_widget_init (GtkListItemWidget *self)
+{
+  GtkEventController *controller;
+  GtkGesture *gesture;
+
+  gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
+
+  gesture = gtk_gesture_click_new ();
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
+                                              GTK_PHASE_BUBBLE);
+  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture),
+                                     FALSE);
+  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture),
+                                 GDK_BUTTON_PRIMARY);
+  g_signal_connect (gesture, "pressed",
+                    G_CALLBACK (gtk_list_item_widget_click_gesture_pressed), self);
+  g_signal_connect (gesture, "released",
+                    G_CALLBACK (gtk_list_item_widget_click_gesture_released), self);
+  g_signal_connect (gesture, "cancel",
+                    G_CALLBACK (gtk_list_item_widget_click_gesture_canceled), self);
+  gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
+
+  controller = gtk_event_controller_focus_new ();
+  g_signal_connect (controller, "enter", G_CALLBACK (gtk_list_item_widget_enter_cb), self);
+  gtk_widget_add_controller (GTK_WIDGET (self), controller);
+
+  self->item = gtk_list_item_new (self);
+}
+
+GtkWidget *
+gtk_list_item_widget_new (GtkListItemFactory *factory,
+                          const char         *css_name)
+{
+  GtkListItemWidget *result;
+
+  g_return_val_if_fail (css_name != NULL, NULL);
+
+  result = g_object_new (GTK_TYPE_LIST_ITEM_WIDGET,
+                         "css-name", css_name,
+                         NULL);
+  if (factory)
+    {
+      result->factory = g_object_ref (factory);
+
+      gtk_list_item_factory_setup (factory, result->item);
+    }
+
+  return GTK_WIDGET (result);
+}
+
+void
+gtk_list_item_widget_bind (GtkListItemWidget *self,
+                           guint              position,
+                           gpointer           item,
+                           gboolean           selected)
+{
+  if (self->factory)
+    gtk_list_item_factory_bind (self->factory, self->item, position, item, selected);
+
+  if (selected)
+    gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED, FALSE);
+  else
+    gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED);
+}
+
+void
+gtk_list_item_widget_rebind (GtkListItemWidget *self,
+                             guint              position,
+                             gpointer           item,
+                             gboolean           selected)
+{
+  if (self->factory)
+    gtk_list_item_factory_rebind (self->factory, self->item, position, item, selected);
+
+  if (selected)
+    gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED, FALSE);
+  else
+    gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED);
+
+  gtk_css_node_invalidate (gtk_widget_get_css_node (GTK_WIDGET (self)), GTK_CSS_CHANGE_ANIMATIONS);
+}
+
+void
+gtk_list_item_widget_update (GtkListItemWidget *self,
+                             guint              position,
+                             gboolean           selected)
+{
+  if (self->factory)
+    gtk_list_item_factory_update (self->factory, self->item, position, selected);
+
+  if (selected)
+    gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED, FALSE);
+  else
+    gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED);
+
+}
+
+void
+gtk_list_item_widget_unbind (GtkListItemWidget *self)
+{
+  if (self->factory)
+    gtk_list_item_factory_unbind (self->factory, self->item);
+
+  gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED);
+
+  gtk_css_node_invalidate (gtk_widget_get_css_node (GTK_WIDGET (self)), GTK_CSS_CHANGE_ANIMATIONS);
+}
+
+void
+gtk_list_item_widget_add_child (GtkListItemWidget *self,
+                                GtkWidget         *child)
+{
+  gtk_widget_set_parent (child, GTK_WIDGET (self));
+}
+
+void
+gtk_list_item_widget_remove_child (GtkListItemWidget *self,
+                                   GtkWidget         *child)
+{
+  gtk_widget_unparent (child);
+}
+
+guint
+gtk_list_item_widget_get_position (GtkListItemWidget *self)
+{
+  return self->item->position;
+}
+
+gpointer
+gtk_list_item_widget_get_item (GtkListItemWidget *self)
+{
+  return self->item->item;
+}
+
diff --git a/gtk/gtklistitemwidgetprivate.h b/gtk/gtklistitemwidgetprivate.h
new file mode 100644 (file)
index 0000000..77db256
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#ifndef __GTK_LIST_ITEM_WIDGET_PRIVATE_H__
+#define __GTK_LIST_ITEM_WIDGET_PRIVATE_H__
+
+#include "gtklistitemfactory.h"
+#include "gtkwidget.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_LIST_ITEM_WIDGET         (gtk_list_item_widget_get_type ())
+#define GTK_LIST_ITEM_WIDGET(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_ITEM_WIDGET, GtkListItemWidget))
+#define GTK_LIST_ITEM_WIDGET_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_ITEM_WIDGET, GtkListItemWidgetClass))
+#define GTK_IS_LIST_ITEM_WIDGET(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_ITEM_WIDGET))
+#define GTK_IS_LIST_ITEM_WIDGET_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_ITEM_WIDGET))
+#define GTK_LIST_ITEM_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_ITEM_WIDGET, GtkListItemWidgetClass))
+
+typedef struct _GtkListItemWidget GtkListItemWidget;
+typedef struct _GtkListItemWidgetClass GtkListItemWidgetClass;
+
+struct _GtkListItemWidget
+{
+  GtkWidget parent_instance;
+
+  GtkListItemFactory *factory;
+  GtkListItem *item;
+};
+
+struct _GtkListItemWidgetClass
+{
+  GtkWidgetClass parent_class;
+
+  void          (* activate_signal)                             (GtkListItemWidget            *self);
+};
+
+GType                   gtk_list_item_widget_get_type           (void) G_GNUC_CONST;
+
+GtkWidget *             gtk_list_item_widget_new                (GtkListItemFactory     *factory,
+                                                                 const char             *css_name);
+
+void                    gtk_list_item_widget_bind               (GtkListItemWidget      *self,
+                                                                 guint                   position,
+                                                                 gpointer                item,
+                                                                 gboolean                selected);
+void                    gtk_list_item_widget_rebind             (GtkListItemWidget      *self,
+                                                                 guint                   position,
+                                                                 gpointer                item,
+                                                                 gboolean                selected);
+void                    gtk_list_item_widget_update             (GtkListItemWidget      *self,
+                                                                 guint                   position,
+                                                                 gboolean                selected);
+void                    gtk_list_item_widget_unbind             (GtkListItemWidget      *self);
+
+void                    gtk_list_item_widget_add_child          (GtkListItemWidget      *self,
+                                                                 GtkWidget              *child);
+void                    gtk_list_item_widget_remove_child       (GtkListItemWidget      *self,
+                                                                 GtkWidget              *child);
+
+guint                   gtk_list_item_widget_get_position       (GtkListItemWidget      *self);
+gpointer                gtk_list_item_widget_get_item           (GtkListItemWidget      *self);
+
+G_END_DECLS
+
+#endif  /* __GTK_LIST_ITEM_WIDGET_PRIVATE_H__ */
index 4cfb95f706fb32aaf6fae3a48e505edf647cad9d..785e63699fe6991610845237872a9507f772e87d 100644 (file)
@@ -281,6 +281,7 @@ gtk_public_sources = files([
   'gtklistitem.c',
   'gtklistitemfactory.c',
   'gtklistitemmanager.c',
+  'gtklistitemwidget.c',
   'gtklistlistmodel.c',
   'gtkliststore.c',
   'gtklistview.c',
index 7556730f2895e0633b10a2df64e05b33358c5c30..927e3a1fd34dc221d5e6178c8ce3163757e37349 100644 (file)
@@ -493,8 +493,6 @@ setup_widget (GtkListItem *list_item,
   RowData *data;
 
   data = g_slice_new0 (RowData);
-  g_signal_connect (list_item, "notify::item", G_CALLBACK (row_data_notify_item), data);
-  g_object_set_data_full (G_OBJECT (list_item), "row-data", data, row_data_free);
 
   box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
   gtk_list_item_set_child (list_item, box);
@@ -516,6 +514,9 @@ setup_widget (GtkListItem *list_item,
   gtk_label_set_max_width_chars (GTK_LABEL (data->name), 25);
   gtk_label_set_ellipsize (GTK_LABEL (data->name), PANGO_ELLIPSIZE_END);
   gtk_box_append (GTK_BOX (box), data->name);
+
+  g_signal_connect (list_item, "notify::item", G_CALLBACK (row_data_notify_item), data);
+  g_object_set_data_full (G_OBJECT (list_item), "row-data", data, row_data_free);
 }
 
 static GListModel *